using System;
using System.Diagnostics;
using SharpDX;
using SharpDX.Direct3D11;
using Buffer = SharpDX.Direct3D11.Buffer;
using System.Runtime.CompilerServices;
using BepuUtilities;
using System.Runtime.InteropServices;
#if MYCODE
using BepuUtilities.Vectors;
#else
using System.Numerics;
#endif

namespace DemoRenderer
{
    public static class Helpers
    {
        /// <summary>
        /// 使用绑定设置索引缓冲区。
        /// </summary>
        /// <param name="inputAssembler">要设置的汇编程序。</param>
        /// <param name="binding">要设置为索引的设备的绑定。</param>
        /// <param name="Offset">从索引缓冲区开始开始读取的偏移量。</param>
        public static void SetIndexBuffer(this InputAssemblerStage inputAssembler, IndexBuffer binding, int offset = 0)
        {
            inputAssembler.SetIndexBuffer(binding.Buffer, binding.Format, offset);
        }

        /// <summary>
        /// 使用UpdateSubresource更新缓冲区。
        /// </summary>
        /// <typeparam name="T">缓冲区中元素的类型。</typeparam>
        /// <param name="context">用于更新缓冲区的上下文。</param>
        /// <param name="buffer">要更新的缓冲区。</param>
        /// <param name="newValues">要上载到缓冲区的值。</param>
        /// <param name="sourceOffset">要从中读取的新值数组中的起始索引。</param>
        /// <param name="count">要上载到缓冲区的值中的元素数。</param>
        /// <param name="destinationOffset">从缓冲区开始的偏移量,用于存储新值。</param>
        public static unsafe void UpdateBuffer<T>(this DeviceContext context, Buffer buffer, Span<T> newValues, int count, int sourceOffset = 0, int destinationOffset = 0) where T : struct
        {
            ref var raw = ref MemoryMarshal.GetReference(newValues);
            fixed (byte* bytes = &Unsafe.As<T, byte>(ref raw))
            {
                var strideInBytes = Unsafe.SizeOf<T>();
                context.UpdateSubresource(
                    new DataBox(new IntPtr(bytes + sourceOffset * strideInBytes)), buffer, 0,
                    new ResourceRegion(
                        destinationOffset * strideInBytes, 0, 0,
                        (destinationOffset + count) * strideInBytes, 1, 1));
            }

        }

        /// <summary>
        /// 为屏幕空间四边形创建指定大小的索引缓冲区。
        /// </summary>
        /// <param name="aquCount">要为其创建索引的四元组数量。</param>
        /// <return>屏幕空间四元组的索引缓冲区。</return>
        /// <remarks>Using redundant indices for batches avoids a slow path for low triangle count instancing. This is hardware/driver specific; it may change on newer cards.</remarks>
        public static uint[] GetQuadIndices(int quadCount)
        {
            var indexData = new uint[quadCount * 6];
            uint baseVertex = 0;
            for (int glyphIndexStart = 0; glyphIndexStart < indexData.Length; glyphIndexStart += 6)
            {
                // 前面的三角形是逆时针方向的。
                // 四边形布局：
                // 0_1
                // //
                // //
                // 2_3
                // 2 0 2 1
                // 1 2 3
                indexData[glyphIndexStart + 0] = baseVertex + 0;
                indexData[glyphIndexStart + 1] = baseVertex + 2;
                indexData[glyphIndexStart + 2] = baseVertex + 1;
                indexData[glyphIndexStart + 3] = baseVertex + 1;
                indexData[glyphIndexStart + 4] = baseVertex + 2;
                indexData[glyphIndexStart + 5] = baseVertex + 3;
                baseVertex += 4;
            }
            return indexData;
        }
        /// <summary>
        /// 为方框创建指定大小的索引缓冲区。
        /// </summary>
        /// <param name="aquCount">要为其创建索引的框数。</param>
        /// <return>框的索引缓冲区。</return>
        /// <remarks>Using redundant indices for batches avoids a slow path for low triangle count instancing. This is hardware/driver specific; it may change on newer cards.</remarks>
        public static uint[] GetBoxIndices(int boxCount)
        {
            // 重复构建AABB网格的索引缓冲区。冗余索引缓冲区往往比实例化微型模型更快。影响因硬件而异。
            var indexData = new uint[boxCount * 36];
            uint baseVertex = 0;
            for (int glyphIndexStart = 0; glyphIndexStart < indexData.Length; glyphIndexStart += 36)
            {
                // 请注意,顺序和缠绕非常重要,并且必须与RenderSpheres.hlsl vs用法一致。
                // 它假设顶点id的3位字符串中的未设置位对应于最小位置：
                // 顶点id 0变为(-1,-1,-1),而顶点id 7变为(1,1,1)。
                // -X
                indexData[glyphIndexStart + 0] = baseVertex + 0;
                indexData[glyphIndexStart + 1] = baseVertex + 4;
                indexData[glyphIndexStart + 2] = baseVertex + 6;
                indexData[glyphIndexStart + 3] = baseVertex + 6;
                indexData[glyphIndexStart + 4] = baseVertex + 2;
                indexData[glyphIndexStart + 5] = baseVertex + 0;
                // +X
                indexData[glyphIndexStart + 6] = baseVertex + 5;
                indexData[glyphIndexStart + 7] = baseVertex + 1;
                indexData[glyphIndexStart + 8] = baseVertex + 3;
                indexData[glyphIndexStart + 9] = baseVertex + 3;
                indexData[glyphIndexStart + 10] = baseVertex + 7;
                indexData[glyphIndexStart + 11] = baseVertex + 5;
                // -Y
                indexData[glyphIndexStart + 12] = baseVertex + 0;
                indexData[glyphIndexStart + 13] = baseVertex + 1;
                indexData[glyphIndexStart + 14] = baseVertex + 5;
                indexData[glyphIndexStart + 15] = baseVertex + 5;
                indexData[glyphIndexStart + 16] = baseVertex + 4;
                indexData[glyphIndexStart + 17] = baseVertex + 0;
                // +Y
                indexData[glyphIndexStart + 18] = baseVertex + 2;
                indexData[glyphIndexStart + 19] = baseVertex + 6;
                indexData[glyphIndexStart + 20] = baseVertex + 7;
                indexData[glyphIndexStart + 21] = baseVertex + 7;
                indexData[glyphIndexStart + 22] = baseVertex + 3;
                indexData[glyphIndexStart + 23] = baseVertex + 2;
                // -Z
                indexData[glyphIndexStart + 24] = baseVertex + 1;
                indexData[glyphIndexStart + 25] = baseVertex + 0;
                indexData[glyphIndexStart + 26] = baseVertex + 2;
                indexData[glyphIndexStart + 27] = baseVertex + 2;
                indexData[glyphIndexStart + 28] = baseVertex + 3;
                indexData[glyphIndexStart + 29] = baseVertex + 1;
                // +Z
                indexData[glyphIndexStart + 30] = baseVertex + 4;
                indexData[glyphIndexStart + 31] = baseVertex + 5;
                indexData[glyphIndexStart + 32] = baseVertex + 7;
                indexData[glyphIndexStart + 33] = baseVertex + 7;
                indexData[glyphIndexStart + 34] = baseVertex + 6;
                indexData[glyphIndexStart + 35] = baseVertex + 4;
                baseVertex += 8;
            }
            return indexData;
        }
        /// <summary>
        /// 以unrm方式打包RGB颜色,以使位0至10为R,位11至21为G,位22至31为B。
        /// </summary>
        /// <param name="color">要打包的RGB颜色。</param>
        /// <return>打包为32位的颜色。</return>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static uint PackColor(in Vector3 color)
        {
            const uint RScale = (1 << 11) - 1;
            const uint GScale = (1 << 11) - 1;
            const uint BScale = (1 << 10) - 1;
            var r = (uint)(color.X * RScale);
            var g = (uint)(color.Y * GScale);
            var b = (uint)(color.Z * BScale);
            if (r > RScale)
                r = RScale;
            if (g > GScale)
                g = GScale;
            if (b > BScale)
                b = BScale;
            return r | (g << 11) | (b << 22);
        }

        /// <summary>
        /// 解包由Helpers.PackColor函数打包的3分量颜色。
        /// </summary>
        /// <param name="packedColor">要解包的颜色的打包表示形式。</param>
        /// <param name="color">解包颜色。</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void UnpackColor(uint packedColor, out Vector3 color)
        {
            // 我们不支持任何形式的alpha,因此我们将11位专门用于R,11位用于G,10位用于B。
            // B存储在最高有效位,R存储在最低有效位。
            color.X = (packedColor & 0x7FF) / (float)((1 << 11) - 1);
            color.Y = ((packedColor >> 11) & 0x7FF) / (float)((1 << 11) - 1);
            color.Z = ((packedColor >> 22) & 0x3FF) / (float)((1 << 10) - 1);
        }

        /// <summary>
        /// 以unrm方式打包RGBA颜色,使位0到7为R,位8到15为G,位16到23为B,位24到31为A。
        /// </summary>
        /// <param name="color">要打包的RGBA颜色。</param>
        /// <return>打包为32位的颜色。</return>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static uint PackColor(in Vector4 color)
        {
            var scaledColor = Vector4.Max(Vector4.Zero, Vector4.Min(Vector4.One, color)) * 255;
            return (uint)scaledColor.X | ((uint)scaledColor.Y << 8) | ((uint)scaledColor.Z << 16) | ((uint)scaledColor.W << 24);
        }

        /// <summary>
        /// 解包由Helpers.PackColor函数打包的4分量颜色。
        /// </summary>
        /// <param name="packedColor">要解包的颜色的打包表示形式。</param>
        /// <param name="color">解包颜色。</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void UnpackColor(uint packedColor, out Vector4 color)
        {
            color = new Vector4(
                (packedColor & 255) / 255f,
                ((packedColor >> 8) & 255) / 255f,
                ((packedColor >> 16) & 255) / 255f,
                ((packedColor >> 24) & 255) / 255f);
        }

        /// <summary>
        /// 通过确保W分量为正,然后将其丢弃,将方向弱打包到3分量向量中。其余零部件保持完全精度。
        /// </summary>
        /// <param name="source">打包方向。</param>
        /// <param name="pack">W-less封装方向,其余分量被否定以确保重建的正W分量有效。</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void PackOrientation(in Quaternion source, out Vector3 packed)
        {
            packed = new Vector3(source.X, source.Y, source.Z);
            if (source.W < 0)
            {
                packed = -packed;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void PackDuplicateZeroSNORM(float source, out ushort packed)
        {
            Debug.Assert(source >= -1 && source <= 1);
            var magnitude = source * ((1 << 15) - 1);
            ref var reinterpreted = ref Unsafe.As<float, uint>(ref magnitude);
            // 缓存符号位并将其移动到位。
            var sign = (int)((reinterpreted & 0x8000_0000u) >> 16);
            // 清除符号位。
            reinterpreted &= 0x7FFF_FFFF;
            packed = (ushort)((int)magnitude | sign);
        }
        /// <summary>
        /// 通过以16位存储每个分量,将方向打包为8个字节。每个分量的最高有效位用作符号位。其余位用于幅值。
        /// </summary>
        /// <param name="source">打包方向。</param>
        /// <param name="pack">打包方向。</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe static ulong PackOrientationU64(ref Quaternion source)
        {
            // 这并不完全是一个聪明的打包,但是对于64位来说,聪明并不是必需的。
            ref var vectorSource = ref Unsafe.As<float, Vector4>(ref source.X);
            var clamped = Vector4.Max(new Vector4(-1), Vector4.Min(new Vector4(1), vectorSource));
            ulong packed;
            ref var packedShorts = ref Unsafe.As<ulong, ushort>(ref *&packed);
            PackDuplicateZeroSNORM(clamped.X, out packedShorts);
            PackDuplicateZeroSNORM(clamped.Y, out Unsafe.Add(ref packedShorts, 1));
            PackDuplicateZeroSNORM(clamped.Z, out Unsafe.Add(ref packedShorts, 2));
            PackDuplicateZeroSNORM(clamped.W, out Unsafe.Add(ref packedShorts, 3));
            return packed;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static unsafe float UnpackDuplicateZeroSNORM(ushort packed)
        {
            var unpacked = (packed & ((1 << 15) - 1)) * (1f / ((1 << 15) - 1));
            ref var reinterpreted = ref Unsafe.As<float, uint>(ref *&unpacked);
            // 设置符号位。
            reinterpreted |= (packed & (1u << 15)) << 16;
            return unpacked;
        }
        public static void UnpackOrientation(ulong packed, out Quaternion orientation)
        {
            ref var packedShorts = ref Unsafe.As<ulong, ushort>(ref packed);
            orientation.X = UnpackDuplicateZeroSNORM(packedShorts);
            orientation.Y = UnpackDuplicateZeroSNORM(Unsafe.Add(ref packedShorts, 1));
            orientation.Z = UnpackDuplicateZeroSNORM(Unsafe.Add(ref packedShorts, 2));
            orientation.W = UnpackDuplicateZeroSNORM(Unsafe.Add(ref packedShorts, 3));
            QuaternionEx.Normalize(ref orientation);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool GetScreenLocation(in Vector3 position, in Matrix viewProjection, in Vector2 resolution, out Vector2 screenLocation)
        {
            Matrix.Transform(new Vector4(position, 1), viewProjection, out var projected);
            projected /= projected.W;
            if (projected.Z <= 0 || MathF.Abs(projected.X) > 1 || MathF.Abs(projected.Y) > 1)
            {
                screenLocation = default;
                return false;
            }
            var ndc = new Vector2(projected.X, projected.Y);
            screenLocation = (ndc * new Vector2(0.5f, -0.5f) + new Vector2(0.5f)) * resolution;
            return true;
        }

        /// <summary>
        /// 将sRGB Gamma曲线应用于标量输入。
        /// </summary>
        /// <param name="x">要应用曲线的值。</param>
        /// <Returns>转换值。</Returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float ToSRGB(float x)
        {
            return MathF.Max(0, MathF.Min(1, x <= 0.0031308f ?
                12.92f * x :
                1.055f * MathF.Pow(x, 1f / 2.4f) - 0.055f));
        }

        /// <summary>
        /// 将sRGB Gamma曲线应用于向量。
        /// </summary>
        /// <param name="线性">要将曲线应用到的线性输入。</param>
        /// <param name="sRGB">转换值。</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void ToSRGB(in Vector3 linear, out Vector3 srgb)
        {
            srgb = new Vector3(ToSRGB(linear.X), ToSRGB(linear.Y), ToSRGB(linear.Z));
        }

        [Conditional("DEBUG")]
        public static void CheckForUndisposed(bool disposed, object o)
        {
            Debug.Assert(disposed, "An object of type " + o.GetType() + " was not disposed prior to finalization.");
        }

        public static void Dispose<T>(ref T disposable) where T : IDisposable
        {
            if (disposable != null)
                disposable.Dispose();
            disposable = default(T);
        }

    }


}
